﻿//#define AST

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;

using PSLib.Parsing.Lua;
using PSLib.Parsing.Lua.ObjectModel;
using Antlr.Runtime.Tree;
using PSLib.Parsing.Common;
using System.Reflection;
using ANTLRTest.Context;

namespace ANTLRTest
{
    enum ParseType
    {
        File,
        Batch
    }

    

    class FollowPresenter
    {
        public FollowPresenter(string ruleOrToken,string inRule, List<string> resultTokens)
        {
            this.RuleOrToken = ruleOrToken;
            this.InRule = inRule;
            this.ResultTokens = resultTokens;
        }
        public string RuleOrToken { get; private set; }
        public string InRule { get; private set; }
        public List<string> ResultTokens { get; private set; }

        public override string ToString()
        {
            string resultTokens = string.Empty;
            foreach (var item in this.ResultTokens)
            {
                resultTokens += item + ", ";
            }
            if (resultTokens != string.Empty) resultTokens = resultTokens.Substring(0, resultTokens.Length - ", ".Length);
            return string.Format("<In [{0}]: {1}> -> {2}", this.InRule, this.RuleOrToken, resultTokens);
        }
    }

    class Program
    {
        public static readonly string DumpOutputDirectory = @"d:\Temp\Logs\";
        public static readonly string BatchStartDirectory = @"x:\Documents\games\World of Warcraft\Interface\AddOns\";

        

        static void FileRun(string filePath,string outputDirectory, bool generateErrorsDump, bool objectModelDump, bool generateTokenDump)
        {
            if(!File.Exists(filePath)) 
            {
                Console.WriteLine(filePath + "file does not exists.");
                return;
            }
            string fileName = Path.GetFileNameWithoutExtension(filePath);


            //Check dump errors option
            if (generateErrorsDump)
            {
                TextWriter errStream = new StreamWriter(string.Format("{0}\\{1}.err", outputDirectory, fileName));
                Console.SetError(errStream);
            }

            DateTime startdate = DateTime.Now;
            //Create stream from file
            Antlr.Runtime.ICharStream st = new Antlr.Runtime.ANTLRFileStream(filePath);

            //Create lua lexer
            LuaLexer lexer = new LuaLexer(st);
            Antlr.Runtime.CommonTokenStream tokenStream = new Antlr.Runtime.CommonTokenStream() { TokenSource = lexer };


            ContextWalker contextWalker = new ContextWalker(typeof(LuaParser),typeof(LuaLexer));
            //Create lua parser
            LuaParser parser = new LuaParser(tokenStream, new Antlr.Runtime.RecognizerSharedState() { errorRecovery = true });
            
            AddManualDefinition(parser.ObjectModelProvider);
            
            //Parse source
            parser.program();
            var sssssss = parser.LastFollowingContext;
            DateTime enddate = DateTime.Now;

            Console.WriteLine("Compile time: {0}", enddate - startdate);
            ObjectModelProvider model = parser.ObjectModelProvider;
            //Close error log
            if (generateErrorsDump) Console.Error.Close();

            //Check dump parsing tree dump option
            if (objectModelDump)
            {
                string parsingTreeFileName = string.Format("{0}\\{1}.treeDump.xml", outputDirectory, fileName);
                string parsingTreeResult = Program.DumpObjectModel(parser.ObjectModelProvider.RootScope);
                File.WriteAllText(parsingTreeFileName, parsingTreeResult);
            }

            //Check dump lexem tree dump option
            if (generateTokenDump)
            {
                string tokensFileName = string.Format("{0}\\{1}.tokenDump.xml", outputDirectory, fileName);
                string tokensResult = Program.DumpTokenStream(tokenStream);
                File.WriteAllText(tokensFileName, tokensResult);
            }

            ElementPosition globalPosition = new ElementPosition(6,17);
            parser.ContextProvider.GetContext(globalPosition);
        }
        static void AddManualDefinition(ObjectModelProvider model)
        {
            //Function func = new Function();
            
            //Function func1 = new Function();
            //func1.Returns.Add(ValueReference.Create("func1 1 return"));
            //func1.Returns.Add(ValueReference.Create("func1 2 return"));

            //func.Returns.Add(ValueReference.Create(func1));
            //func.Returns.Add(ValueReference.Create("func 2 return"));

            //model.GlobalVariables.Add("SomeFunction", ValueReference.Create(func));
        }

        static void BatchRun(string batchStartDirectory, string outputDirectory, bool generateErrorsDump, bool objectModelDump, bool generateTokenDump)
        {
            string[] files = System.IO.Directory.GetFiles(batchStartDirectory, "*.lua", System.IO.SearchOption.AllDirectories);
            foreach (var item in files)
                FileRun(item, outputDirectory, generateErrorsDump, objectModelDump, generateTokenDump);
        }

        static string DumpObjectModel(Scope scope)
        {
            if (scope == null) return "";
            XmlDocument doc = new XmlDocument();
            XmlNode node = doc.CreateElement("ScopeDump");
            DumpParsedTree_r(node, scope);
            
            doc.AppendChild(node);
            return doc.InnerXml;
        }

        static void DumpVariableMetatableValue_r(XmlNode root, Metatable table)
        {
            foreach (var variable in table)
            {
                DumpVariableValue_r(root, variable.Value);
            }
        }

        static void DumpVariableValueRevisions_r(XmlNode root, Variable variable)
        {
            foreach (var revision in variable.ValueReferenceRevisions)
            {
                XmlNode revisionNode = root.OwnerDocument.CreateElement("revision");
                revisionNode.AddAttribute("pos", revision.Position.ToString());
                revisionNode.AddAttribute("type", revision.ValueReference.Type.ToString());
                switch (revision.ValueReference.Type)
                {
                    case ValueReferenceType.Nil:
                        break;
                    case ValueReferenceType.Number:
                    case ValueReferenceType.String:
                    case ValueReferenceType.Boolean:
                        revisionNode.AddAttribute("value", revision.ValueReference.Value.ToString());
                        break;
                    case ValueReferenceType.Function:
                        foreach (var item in (revision.ValueReference.Value as Function).Arguments)
                        {
                            XmlNode funcArgumentNode = root.OwnerDocument.CreateElement("a");
                            funcArgumentNode.AddAttribute("name", item.Name);
                            if (!string.IsNullOrEmpty(item.Description)) funcArgumentNode.AddAttribute("description", item.Description);
                            revisionNode.AppendChild(funcArgumentNode);
                        }
                        
                        break;
                    case ValueReferenceType.Table:
                        DumpVariableMetatableValue_r(revisionNode, revision.ValueReference.Value as Metatable);
                        break;
                    default:
                        break;
                }
                root.AppendChild(revisionNode);
            }
        }
        static void DumpVariableValue_r(XmlNode root, Variable variable)
        {
            XmlNode varNode = root.OwnerDocument.CreateElement("v");
            varNode.AddAttribute("name", variable.ToString());
            DumpVariableValueRevisions_r(varNode, variable);
            root.AppendChild(varNode);
        }

        static void DumpParsedTree_r(XmlNode root, Scope scope)
        {
            XmlNode node = root.OwnerDocument.CreateElement("e");

            node.AddAttribute("name", scope.Name);
            node.AddAttribute("span", scope.Span.ToString());

            if (scope.Variables.Count != 0)
            {
                XmlNode varsNode = root.OwnerDocument.CreateElement("variables");
                foreach (var item in scope.Variables)
                {
                    DumpVariableValue_r(varsNode, item);
                }

                node.AppendChild(varsNode);
            }

            foreach (var item in scope.ChildScopes)
            {
                DumpParsedTree_r(node, item);
            }
            root.AppendChild(node);
        }

        static string DumpTokenStream(Antlr.Runtime.CommonTokenStream stream)
        {
            XmlDocument doc = new XmlDocument();
            XmlNode root = doc.CreateElement("tokens");

            foreach (Antlr.Runtime.IToken token in stream.GetTokens())
            {
                XmlNode node = root.OwnerDocument.CreateElement("t");
                node.AddAttribute("type", LuaParser.tokenNames[token.Type]);
                node.AddAttribute("value", token.Text);
                node.AddAttribute("channel", token.Channel.ToString());
                XmlNode posNode = root.OwnerDocument.CreateElement("pos");
                posNode.AddAttribute("line", token.Line.ToString());
                posNode.AddAttribute("start", token.CharPositionInLine.ToString());
                posNode.AddAttribute("stop", (token.CharPositionInLine + token.Text.Length).ToString());
                node.AppendChild(posNode);
                root.AppendChild(node);
            }
            doc.AppendChild(root);
            return doc.InnerXml;
        }
        
        static void Main(string[] args)
        {
            ParseType type = ParseType.File;

            switch (type)
            {
                case ParseType.File:
                    Program.FileRun("test.lua", Program.DumpOutputDirectory, false, true, true);
                    break;
                case ParseType.Batch:
                    Program.BatchRun(Program.BatchStartDirectory, Program.DumpOutputDirectory, true, true, true);
                    break;
                default:
                    Console.WriteLine("Unknown parse type.");
                    break;
            }

            Console.WriteLine("Completed");
            Console.ReadKey();
        }
    }
}
